Skip to content

feat(calendar): redesign calendar apps using plain HTML/CSS/TS + Web Components#717

Merged
nicomiguelino merged 48 commits intomasterfrom
feat/overhaul-calendar-app
Mar 25, 2026
Merged

feat(calendar): redesign calendar apps using plain HTML/CSS/TS + Web Components#717
nicomiguelino merged 48 commits intomasterfrom
feat/overhaul-calendar-app

Conversation

@nicomiguelino
Copy link
Contributor

@nicomiguelino nicomiguelino commented Mar 2, 2026

Summary

  • Migrate calendar app from Vue 3 + Pinia to framework-free Web Components
  • Add reusable calendar-views components (weekly, daily, schedule) to edge-apps-library
  • Migrate google-calendar app to use shared Web Components from edge-apps-library
  • Add outlook-calendar app built on Microsoft Graph API using the same shared components

Changes by app

edge-apps-library

  • Add calendar-views/ directory with weekly-calendar-view, daily-calendar-view, and schedule-calendar-view custom elements
  • Add shared calendar-view-utils.ts and event-layout.ts for layout computation
  • Export CalendarEvent, CalendarViewMode, and CALENDAR_VIEW_MODE as shared types
  • Fix getDateRangeForViewMode to use dayjs(Date.now()) so test mocks work correctly
  • Optimize weekly view to skip full re-renders on every now tick — only update time indicator

calendar

  • Replace Vue app with custom elements using shared calendar-views components
  • Refactor iCal fetching with explicit timezone and locale awareness
  • Remove local types.ts; import shared types directly from @screenly/edge-apps
  • Add screenshot e2e coverage across all three modes and 10 resolutions

google-calendar

  • Migrate to shared calendar-views components from edge-apps-library
  • Extend base CalendarEvent type with colorId for Google-specific coloring
  • Update README to document actual Google Calendar API integration and settings
  • Add screenshot e2e coverage across all three modes and 10 resolutions

outlook-calendar

  • New app backed by Microsoft Graph /calendarview API
  • Add $top=100 query param to avoid silent truncation of Graph API results (default page size is 10)
  • Use IANA timezone names in screenshot mock data (dayjs requires IANA, not Windows timezone names)
  • Add screenshot e2e coverage across all three modes and 10 resolutions

- Migrate from Vue 3 + Pinia to framework-free Web Components
- Add reusable weekly-calendar-view custom element to edge-apps-library

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nicomiguelino nicomiguelino self-assigned this Mar 2, 2026
@github-actions
Copy link

github-actions bot commented Mar 2, 2026

PR Reviewer Guide 🔍

(Review updated until commit 60f9ed0)

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Range mismatch

The fetch window for schedule mode still resolves to the current month, while the new schedule UI shows today and tomorrow. On the last day of a month, tomorrow's events can fall outside the fetched range and disappear from the schedule view.

const getDateRangeForViewMode = (viewMode: ViewMode, timezone: string) => {
  const nowInTimezone = dayjs().tz(timezone)
  const todayInTimezone = nowInTimezone.startOf('day')

  let startDate: Date
  let endDate: Date

  if (viewMode === VIEW_MODE.DAILY) {
    startDate = todayInTimezone.toDate()
    endDate = todayInTimezone.add(1, 'day').toDate()
  } else if (viewMode === VIEW_MODE.WEEKLY) {
    const weekStart = todayInTimezone.startOf('week')
    startDate = weekStart.toDate()
    endDate = weekStart.add(7, 'days').toDate()
  } else {
    const monthStart = todayInTimezone.startOf('month')
    startDate = monthStart.toDate()
    endDate = monthStart.add(1, 'month').toDate()
  }
Overnight events

Window filtering only keeps events whose start date matches the rendered day. Events that begin before midnight and continue into the visible day or 12-hour window will be dropped instead of being clipped into view.

return events.filter((event) => {
  if (event.isAllDay) return false
  const eventStart = dayjs(event.startTime).tz(tz)
  if (eventStart.format('YYYY-MM-DD') !== dayDateStr) return false
  const startH = eventStart.hour() + eventStart.minute() / 60
  const endDt = dayjs(event.endTime).tz(tz)
  const endH = endDt.hour() + endDt.minute() / 60
  const normStart = startH < windowStartHour ? startH + 24 : startH
  const normEnd = endH <= windowStartHour ? endH + 24 : endH
  return normStart < normalizedWindowEnd && normEnd > windowStartHour
Key collision

Event layout lookup keys are derived only from start time, end time, and title. Distinct events with identical timestamps and titles will overwrite each other in the layout map, which can cause overlapping events to render in the same slot.

export const getEventKey = (event: CalendarEvent): string =>
  `${event.startTime}|${event.endTime}|${event.title || ''}`

@github-actions
Copy link

github-actions bot commented Mar 2, 2026

PR Code Suggestions ✨

Latest suggestions up to 60f9ed0
Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix overlapping window filtering

This filter drops events that overlap the visible window but started before
windowStartHour, and it also hides overnight events that began on the previous day.
Compare the event's actual start/end timestamps against the day's window instead of
requiring the start date to equal dayDateStr.

edge-apps/edge-apps-library/src/components/weekly-calendar-view/weekly-calendar-view-utils.ts [151-171]

 export function filterEventsForWindow(
   events: CalendarEvent[],
   dayDateStr: string,
   windowStartHour: number,
   tz: string,
 ): CalendarEvent[] {
-  const windowEndHour = (windowStartHour + 12) % 24
-  const normalizedWindowEnd =
-    windowEndHour <= windowStartHour ? windowEndHour + 24 : windowEndHour
+  const dayStart = dayjs.tz(`${dayDateStr}T00:00:00`, tz)
+  const windowStart = dayStart.hour(windowStartHour).minute(0).second(0).millisecond(0)
+  const windowEnd = windowStart.add(12, 'hour')
+
   return events.filter((event) => {
     if (event.isAllDay) return false
+
     const eventStart = dayjs(event.startTime).tz(tz)
-    if (eventStart.format('YYYY-MM-DD') !== dayDateStr) return false
-    const startH = eventStart.hour() + eventStart.minute() / 60
-    const endDt = dayjs(event.endTime).tz(tz)
-    const endH = endDt.hour() + endDt.minute() / 60
-    const normStart = startH < windowStartHour ? startH + 24 : startH
-    const normEnd = endH <= windowStartHour ? endH + 24 : endH
-    return normStart < normalizedWindowEnd && normEnd > windowStartHour
+    const eventEnd = dayjs(event.endTime).tz(tz)
+
+    return eventStart.isBefore(windowEnd) && eventEnd.isAfter(windowStart)
   })
 }
Suggestion importance[1-10]: 8

__

Why: This correctly identifies that filterEventsForWindow excludes events that overlap the visible 12-hour window when they start before windowStartHour, including cross-midnight overlaps. Comparing full start/end datetimes against the computed window is a meaningful correctness fix for both daily and weekly rendering.

Medium
Always signal readiness

A failure in initialization before the final line prevents signalReady() from ever
running, which can leave the Screenly app stuck in a loading state. Wrap startup in
try/finally so readiness is always signaled even when locale, timezone, or DOM setup
fails.

edge-apps/calendar/src/main.ts [25-75]

 document.addEventListener('DOMContentLoaded', async () => {
-  const scaler = document.querySelector('auto-scaler')
-  scaler?.addEventListener('scalechange', centerAutoScalerVertically)
-  window.addEventListener('resize', centerAutoScalerVertically)
-  centerAutoScalerVertically()
-  setupErrorHandling()
-  setupTheme()
+  try {
+    const scaler = document.querySelector('auto-scaler')
+    scaler?.addEventListener('scalechange', centerAutoScalerVertically)
+    window.addEventListener('resize', centerAutoScalerVertically)
+    centerAutoScalerVertically()
+    setupErrorHandling()
+    setupTheme()
 
-  const calendarMode = (screenly.settings.calendar_mode as string) || 'schedule'
+    const calendarMode = (screenly.settings.calendar_mode as string) || 'schedule'
 
-  const scheduleEl = document.getElementById(
-    'schedule-calendar',
-  ) as ScheduleCalendarView
-  const weeklyEl = document.getElementById(
-    'weekly-calendar',
-  ) as WeeklyCalendarView
-  const dailyEl = document.getElementById('daily-calendar') as DailyCalendarView
+    const scheduleEl = document.getElementById(
+      'schedule-calendar',
+    ) as ScheduleCalendarView
+    const weeklyEl = document.getElementById(
+      'weekly-calendar',
+    ) as WeeklyCalendarView
+    const dailyEl = document.getElementById('daily-calendar') as DailyCalendarView
 
-  const activeEl =
-    calendarMode === 'daily'
-      ? dailyEl
-      : calendarMode === 'weekly'
-        ? weeklyEl
-        : scheduleEl
+    const activeEl =
+      calendarMode === 'daily'
+        ? dailyEl
+        : calendarMode === 'weekly'
+          ? weeklyEl
+          : scheduleEl
 
-  activeEl.classList.add('active')
+    activeEl.classList.add('active')
 
-  const timezone = await getTimeZone()
-  const locale = await getLocale()
-  activeEl.setAttribute('timezone', timezone)
-  activeEl.setAttribute('locale', locale)
+    const timezone = await getTimeZone()
+    const locale = await getLocale()
+    activeEl.setAttribute('timezone', timezone)
+    activeEl.setAttribute('locale', locale)
 
-  const tick = () => {
-    activeEl.now = new Date()
+    const tick = () => {
+      activeEl.now = new Date()
+    }
+    tick()
+    setInterval(tick, 30_000)
+
+    const refresh = async () => {
+      try {
+        const events = await fetchCalendarEventsFromICal({ timezone })
+        activeEl.events = events
+      } catch (error) {
+        console.error('Failed to fetch calendar events:', error)
+      }
+    }
+    await refresh()
+    setInterval(refresh, EVENTS_REFRESH_INTERVAL)
+  } catch (error) {
+    console.error('Failed to initialize calendar:', error)
+  } finally {
+    signalReady()
   }
-  tick()
-  setInterval(tick, 30_000)
-
-  const refresh = async () => {
-    try {
-      const events = await fetchCalendarEventsFromICal({ timezone })
-      activeEl.events = events
-    } catch (error) {
-      console.error('Failed to fetch calendar events:', error)
-    }
-  }
-  await refresh()
-  setInterval(refresh, EVENTS_REFRESH_INTERVAL)
-
-  signalReady()
 })
Suggestion importance[1-10]: 7

__

Why: This is a sound resilience improvement because an exception before the final signalReady() can leave the app stuck loading. Wrapping initialization in try/finally preserves Screenly readiness behavior without changing the main flow of src/main.ts.

Medium
Include ongoing events

This excludes events that are already in progress, so an active meeting disappears
from the schedule as soon as its start time passes. Filter on endTime > now and
startTime < startOfTomorrow so ongoing events remain visible until they actually
end.

edge-apps/edge-apps-library/src/components/schedule-calendar-view/schedule-calendar-view.ts [73-79]

 const todayAll = this._events
   .filter((e) => {
     if (e.isAllDay) return false
     const start = dayjs(e.startTime).tz(tz)
-    return start.isAfter(nowInTz) && start.isBefore(startOfTomorrow)
+    const end = dayjs(e.endTime).tz(tz)
+    return end.isAfter(nowInTz) && start.isBefore(startOfTomorrow)
   })
   .sort(sortByStart)
Suggestion importance[1-10]: 6

__

Why: The current todayAll filter removes events as soon as their start time passes, so ongoing items vanish from schedule-calendar-view before they end. Using endTime to keep active events visible improves schedule accuracy, though the impact is limited to one view.

Low

Previous suggestions

Suggestions up to commit 7378c5c
CategorySuggestion                                                                                                                                    Impact
Possible issue
Include overlapping events in range

The current range filter only includes events whose start is inside the range,
which drops events that start before the window but overlap into it (common for long
meetings). Filter by range overlap (start < endDate && end > startDate) so ongoing
events are included.

edge-apps/calendar-new/src/events.ts [83-99]

 const event = new ical.Event(vevent)
 const eventStart = event.startDate.toJSDate()
-const eventTimestamp = eventStart.getTime()
+const eventEnd = event.endDate?.toJSDate?.() ?? eventStart
 
-if (eventTimestamp >= endTimestamp || eventTimestamp < startTimestamp) {
+const eventStartTs = eventStart.getTime()
+const eventEndTs = eventEnd.getTime()
+
+// Include events that overlap the requested window
+if (eventStartTs >= endTimestamp || eventEndTs <= startTimestamp) {
   return
 }
-
-const eventEnd = event.endDate.toJSDate()
 
 events.push({
   title: event.summary || 'Busy',
   startTime: eventStart.toISOString(),
   endTime: eventEnd.toISOString(),
   isAllDay: event.startDate.isDate,
 })
Suggestion importance[1-10]: 8

__

Why: The current filter only checks whether an event’s start time falls within [startDate, endDate), which incorrectly drops events that begin before the window but overlap into it. The proposed overlap check fixes a real correctness issue in fetchCalendarEventsFromICal() and the improved_code reflects the intended logic.

Medium
Avoid layout key collisions

Using getEventKey() can collide when multiple events share the same start/end/title,
causing incorrect layout lookups (overlaps rendered wrong). Store layouts keyed by
the event object reference instead (e.g., WeakMap<CalendarEvent, EventLayout>),
since the same objects are used during the render pass.

edge-apps/edge-apps-library/src/components/weekly-calendar-view/weekly-calendar-view.ts [5-355]

 import {
   type CalendarEvent,
   type EventLayout,
   findEventClusters,
   calculateClusterLayouts,
-  getEventKey,
 } from './event-layout.js'
 ...
-private _getEventLayouts(): Map<string, EventLayout> {
+private _getEventLayouts(): WeakMap<CalendarEvent, EventLayout> {
   const tz = this._timezone
   const weekStartDate = dayjs(this._getWeekStart()).tz(tz)
-  const layoutMap = new Map<string, EventLayout>()
+  const layoutMap = new WeakMap<CalendarEvent, EventLayout>()
   ...
-        layoutMap.set(getEventKey(event), layout)
+        layoutMap.set(event, layout)
   ...
   return layoutMap
 }
 ...
-    const key = getEventKey(event)
-    const layout = eventLayouts.get(key) ?? {
+    const layout = eventLayouts.get(event) ?? {
       event,
       column: 0,
       columnSpan: 1,
       totalColumns: 1,
     }
Suggestion importance[1-10]: 7

__

Why: Keying layouts by getEventKey() can collide when multiple events share the same startTime/endTime/title, causing eventLayouts.get(key) to return the wrong EventLayout. Switching to a WeakMap<CalendarEvent, EventLayout> keyed by the event object reference avoids collisions and fits the current flow where the same event objects are passed through clustering and rendering.

Medium
Reduce costly rerenders frequency

Updating now every second forces a full calendar re-render each second (the
component rebuilds the entire DOM), which can cause unnecessary CPU usage and jank.
Update on minute boundaries instead, which is sufficient for the time indicator and
dramatically reduces work.

edge-apps/calendar-new/src/main.ts [27-32]

 const tick = () => {
   calendarEl.now = new Date()
 }
 tick()
-setInterval(tick, 1000)
 
+const scheduleNextMinuteTick = () => {
+  const now = new Date()
+  const msUntilNextMinute =
+    (60 - now.getSeconds()) * 1000 - now.getMilliseconds()
+
+  setTimeout(() => {
+    tick()
+    setInterval(tick, 60_000)
+  }, msUntilNextMinute)
+}
+
+scheduleNextMinuteTick()
+
Suggestion importance[1-10]: 6

__

Why: Updating calendarEl.now every second triggers WeeklyCalendarView’s full _render() each second, which is unnecessarily expensive since the UI only needs minute-level updates for the time indicator. The suggested minute-boundary scheduling is a reasonable performance improvement, though not strictly required for correctness.

Low

nicomiguelino and others added 18 commits March 10, 2026 16:16
- Fix missing error cause in events.ts
- Extract CSS and utility functions into separate files to satisfy max-lines rules
- Add e2e screenshots

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… file

- Update `.this-week-title` to Inter Regular (weight 400), normal style, centered, with letter-spacing
- Remove unused `weekly-calendar-view.css` (CSS lives in `weekly-calendar-view-styles.ts`)
- Increase day name font size to 0.98rem with line-height 1.333
- Increase day date font size to 1.469rem with line-height 1.556
- Add opacity 0.8 to today's day name
- Increase day header height and time gutter padding-top to 5.225rem
- Increase time gutter width to 6.5rem and padding-right to 1rem
- Match time label font size to day name (0.98rem)
- Set day-body overflow to visible so the time indicator dot is not clipped
- Adjust current time indicator dot left offset to -0.27rem
- Remove horizontal margin from .header to fix app header alignment
- Force landscape orientation with letterbox bars and vertical centering
- Fix app header margins and compact event display for short events
- Fix timezone-aware day header date and locale-aware day names
- Match Figma design tokens for event card and title styles
- Replace --calendar-accent-color with --theme-color-primary
- Style app-header background and text color from theme
- Make weekly-calendar-view background transparent
- Add `daily-calendar-view` Web Component with same styling as weekly view
- Add `calendar-view-utils.ts` with shared `buildTimeGutter` and `buildEventElement`
- Extract `filterEventsForWindow` into shared utils to eliminate duplication
- Support `calendar_mode` setting to switch between weekly and daily views

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add `schedule-calendar-view` Web Component showing today/tomorrow events
- Move `calendar-view-utils.ts` to components root (shared by all views)
- Fix `generateTimeSlots` to use configured timezone for time labels
- Support `schedule` as default `calendar_mode` in calendar app

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add per-mode screenshot loops (schedule, weekly, daily)
- Rename files from `WxH.png` to `{mode}-WxH.png`
- Enrich ICS mock data with more events for schedule view
- Set screenly_color_accent to #2E8B57 in mock settings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move icon.svg from static/ to static/img/
- Update icon URL in manifest files to match new path
- Remove unused bg.webp from static/images/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename calendar-new to calendar, replacing the old Vue 3/Pinia app
- Add schedule, weekly, and daily calendar views as Web Components
- Remove Vue, Vite, and blueprint dependencies
- Update build tooling to use edge-apps-scripts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the Calendar edge app from a Vue 3 + Pinia implementation to framework-free Web Components, and introduces reusable calendar view components in edge-apps-library (daily/weekly/schedule) with shared event layout + rendering utilities.

Changes:

  • Add <weekly-calendar-view>, <daily-calendar-view>, and <schedule-calendar-view> Web Components (plus shared event layout + DOM builder utilities) to edge-apps-library.
  • Rewrite the Calendar app to render those components from plain HTML/CSS/TS, including new screenshot-test coverage and updated assets.
  • Remove Vue/Vite/Vitest/Pinia scaffolding from the Calendar app and update manifests/docs accordingly.

Reviewed changes

Copilot reviewed 46 out of 87 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
edge-apps/edge-apps-library/src/components/weekly-calendar-view/weekly-calendar-view.ts New weekly view Web Component rendering a 7-day grid with event layout + current-time indicator.
edge-apps/edge-apps-library/src/components/weekly-calendar-view/weekly-calendar-view-utils.ts Shared helpers for windowing, slot generation, event styling/clipping, and safe attribute setting.
edge-apps/edge-apps-library/src/components/weekly-calendar-view/weekly-calendar-view-styles.ts Shadow-DOM CSS for the weekly view.
edge-apps/edge-apps-library/src/components/weekly-calendar-view/index.ts Public exports for weekly view component + event type.
edge-apps/edge-apps-library/src/components/weekly-calendar-view/event-layout.ts Event overlap clustering + column layout algorithm for time-grid views.
edge-apps/edge-apps-library/src/components/vite-env.d.ts Add *.css?inline module typing for component CSS imports.
edge-apps/edge-apps-library/src/components/schedule-calendar-view/schedule-calendar-view.ts New schedule list view Web Component (today + tomorrow, capped).
edge-apps/edge-apps-library/src/components/schedule-calendar-view/schedule-calendar-view-styles.ts Shadow-DOM CSS for the schedule view.
edge-apps/edge-apps-library/src/components/schedule-calendar-view/index.ts Public export for schedule view component.
edge-apps/edge-apps-library/src/components/daily-calendar-view/daily-calendar-view.ts New daily view Web Component (12-hour window, event layout, current-time indicator).
edge-apps/edge-apps-library/src/components/daily-calendar-view/daily-calendar-view-styles.ts Shadow-DOM CSS for the daily view.
edge-apps/edge-apps-library/src/components/daily-calendar-view/index.ts Public export for daily view component.
edge-apps/edge-apps-library/src/components/calendar-view-utils.ts Shared DOM builders for time gutter + event cards used by daily/weekly views.
edge-apps/edge-apps-library/src/components/register.ts Register new calendar custom elements alongside existing components.
edge-apps/edge-apps-library/src/components/index.ts Export new calendar view components (and CalendarEvent type) from the library entrypoint.
edge-apps/edge-apps-library/package.json Add dayjs dependency needed by the new components/utilities.
edge-apps/edge-apps-library/bun.lock Lockfile update for added dependency.
edge-apps/calendar/src/types.ts New local types (CalendarEvent, ViewMode, VIEW_MODE) for the rewritten app.
edge-apps/calendar/src/main.ts Rewrite app bootstrap to use Web Components, theme/error setup, and periodic ticking/refresh.
edge-apps/calendar/src/events.ts Update iCal fetching/parsing to be framework-free and accept explicit timezone input.
edge-apps/calendar/src/css/style.css New global app styles (layout + view switching via .active).
edge-apps/calendar/index.html Replace Vue mount point with static layout using auto-scaler, app-header, and calendar view elements.
edge-apps/calendar/package.json Switch scripts/tooling to edge-apps-scripts workflow; remove Vue/Pinia/Vitest plumbing.
edge-apps/calendar/README.md Update deployment/dev/testing/screenshot instructions for the new architecture.
edge-apps/calendar/e2e/screenshots.spec.ts Add Playwright-based screenshot generation test across modes and resolutions.
edge-apps/calendar/screenly.yml Update manifest icon URL path.
edge-apps/calendar/screenly_qc.yml Update QC manifest icon URL path.
edge-apps/calendar/.ignore Add ignore file for deployment packaging (node_modules).
edge-apps/calendar/.gitignore Add gitignore for build artifacts/logs/etc.
edge-apps/calendar/vitest.config.ts Remove Vitest config (no longer using Vitest).
edge-apps/calendar/vite.config.ts Remove Vite config (now handled by edge-apps-scripts).
edge-apps/calendar/tsconfig.vitest.json Remove Vitest TS config.
edge-apps/calendar/tsconfig.node.json Remove node/tooling TS config.
edge-apps/calendar/tsconfig.json Remove TS project references for the prior Vue setup.
edge-apps/calendar/tsconfig.app.json Remove Vue app TS config.
edge-apps/calendar/src/utils.ts Remove re-export utilities tied to the old blueprint/Vue setup.
edge-apps/calendar/src/test-setup.ts Remove prior Vitest setup.
edge-apps/calendar/src/stores/settings.ts Remove Pinia settings store.
edge-apps/calendar/src/stores/calendar.ts Remove Pinia calendar store.
edge-apps/calendar/src/constants.ts Remove old constants re-export.
edge-apps/calendar/src/components/tests/App.spec.ts Remove Vue unit test.
edge-apps/calendar/src/assets/main.scss Remove old SCSS styling tied to Vue blueprint layout.
edge-apps/calendar/src/App.vue Remove Vue root component.
edge-apps/calendar/playwright.config.ts Remove old Playwright config (now using shared screenshot helpers).
edge-apps/calendar/eslint.config.ts Remove Vue/Vitest/Playwright-specific ESLint config (now via edge-apps-scripts).
edge-apps/calendar/e2e/vue.spec.ts Remove old Vue-focused Playwright test.
edge-apps/calendar/e2e/tsconfig.json Remove old e2e TS config.
edge-apps/calendar/.vscode/extensions.json Remove Vue/Vitest editor recommendations.
edge-apps/calendar/screenshots/weekly-800x480.webp Add updated weekly-mode screenshot artifact.
edge-apps/calendar/screenshots/weekly-720x1280.webp Add updated weekly-mode screenshot artifact.
edge-apps/calendar/screenshots/weekly-480x800.webp Add updated weekly-mode screenshot artifact.
edge-apps/calendar/screenshots/schedule-800x480.webp Add updated schedule-mode screenshot artifact.
edge-apps/calendar/screenshots/schedule-720x1280.webp Add updated schedule-mode screenshot artifact.
edge-apps/calendar/screenshots/schedule-480x800.webp Add updated schedule-mode screenshot artifact.
edge-apps/calendar/screenshots/daily-800x480.webp Add updated daily-mode screenshot artifact.
edge-apps/calendar/screenshots/daily-720x1280.webp Add updated daily-mode screenshot artifact.
edge-apps/calendar/screenshots/daily-480x800.webp Add updated daily-mode screenshot artifact.
edge-apps/calendar/screenshots/daily-1280x720.webp Add updated daily-mode screenshot artifact.
edge-apps/calendar/screenshots/daily-1080x1920.webp Add updated daily-mode screenshot artifact.
edge-apps/calendar/static/images/enable-google-calendar-api.png Include static documentation image asset for setup instructions.
edge-apps/calendar/static/images/authorization-code.png Include static documentation image asset for setup instructions.
edge-apps/calendar/src/assets/font/Aeonik-Regular.woff2 Include font asset (legacy/compat).
edge-apps/calendar/src/assets/font/Aeonik-Regular.woff Include font asset (legacy/compat).
edge-apps/calendar/public/fonts/Aeonik-Regular.woff2 Include font asset served from public/.
edge-apps/calendar/public/fonts/Aeonik-Regular.woff Include font asset served from public/.
edge-apps/calendar/public/favicon.ico Include app favicon asset.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

nicomiguelino and others added 3 commits March 18, 2026 07:12
- Add white variant of screenly.svg to e2e/
- Set screenly_logo_light to a base64 data URI in screenshot tests
- Regenerate screenshots

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Throttle now tick from 1s to 30s to reduce unnecessary re-renders
- Replace JSON.parse for bypass_cors with getSettingWithDefault
- Replace toLocaleString/parseInt with dayjs for hour/minute extraction
- Move customElements.define into each calendar view component file
- Remove redundant side-effect imports and define blocks from register.ts
- Hoist ALLOWED_ATTRIBUTES to module-level constant in weekly-calendar-view-utils

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nicomiguelino nicomiguelino marked this pull request as ready for review March 18, 2026 18:07
nicomiguelino and others added 5 commits March 19, 2026 07:32
…re-renders

- Fix filterEventsForWindow to use absolute datetimes, correctly including events overlapping the window from the left
- Fix getEventStyle to use ms-based arithmetic instead of hour normalization, fixing clippedTop/clippedBottom and topPct/heightPct
- Optimize weekly view now setter to only update time indicator position when window hour hasn't changed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ecture

- Extract `initTokenRefreshLoop` to edge-apps-library to avoid duplication
- Remove local `initTokenRefreshLoop` from `google-calendar` and import from library
- Add new `outlook-calendar` app based on `google-calendar` architecture
  - `src/main.ts`: identical setup flow, calls Microsoft Graph API fetcher
  - `src/events.ts`: fetch from Microsoft Graph API with dayjs timezone handling
  - Manifests copied from `outlook-calendar-old` with updated icon path
  - Screenshot e2e spec with Microsoft Graph API mock data
- Rename old Vue-based app to `outlook-calendar-old`

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sults

- Add GRAPH_MAX_EVENTS constant set to 100
- Pass $top to /calendarview to override the default page size of 10

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…a and add screenshots

- Replace 'Eastern Standard Time' with 'America/New_York' in mock response
- Add initial screenshots for all three calendar modes across all resolutions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the calendar Edge Apps (iCal, Google, Outlook) from a Vue/Pinia implementation to framework-free Web Components, while adding reusable calendar view components (weekly/daily/schedule) and shared utilities to edge-apps-library. It also introduces screenshot-based Playwright coverage across multiple view modes and resolutions.

Changes:

  • Replace Vue app shells with plain HTML/CSS/TS bootstraps that render <schedule-calendar-view>, <weekly-calendar-view>, and <daily-calendar-view>.
  • Add reusable calendar view Web Components plus shared layout/windowing utilities (event clustering/column layout, 12-hour window logic, clipping, current-time indicator).
  • Add shared utilities (timezone-aware date range helper, auto-scaler vertical centering, token refresh loop) and screenshot e2e tests.

Reviewed changes

Copilot reviewed 118 out of 240 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
edge-apps/outlook-calendar/src/main.ts New framework-free bootstrap: selects active view, sets locale/timezone, refreshes events, starts token refresh loop.
edge-apps/outlook-calendar/src/events.ts Refactors Graph event fetching to use shared date-range utility + calendar_id selection and $top.
edge-apps/google-calendar/src/main.ts New framework-free bootstrap mirroring Outlook app flow with token refresh loop and view selection.
edge-apps/google-calendar/src/events.ts Refactors Google Calendar fetching to shared date-range utility and runtime settings.
edge-apps/calendar/src/main.ts New framework-free bootstrap for iCal app (active view selection, periodic refresh, locale/timezone setup).
edge-apps/calendar/src/events.ts Refactors iCal fetching to shared date-range utility and typed settings input; improves error chaining.
edge-apps/*/index.html Replaces Vue mount point with <auto-scaler> + header + three calendar view custom elements.
edge-apps/*/src/css/style.css Adds base layout CSS and active view toggling for framework-free apps.
edge-apps/*/package.json Swaps Vue/Vite-centric scripts/deps for shared edge-apps-scripts workflow; updates build/dev/test commands.
edge-apps/*/README.md Updates setup/deploy/config/testing/screenshot docs for the new framework-free implementation.
edge-apps/*/e2e/screenshots.spec.ts Adds screenshot-based Playwright coverage across modes and resolutions using shared screenshot helpers.
edge-apps/*/screenly.yml Updates manifest icon paths to static/img/icon.svg.
edge-apps/*/screenly_qc.yml Updates QC manifest icon paths to static/img/icon.svg.
edge-apps/edge-apps-library/src/utils/oauth.ts Adds initTokenRefreshLoop() utility with exponential backoff semantics.
edge-apps/edge-apps-library/src/utils/screen.ts Adds centerAutoScalerVertically() utility for fixed-orientation apps.
edge-apps/edge-apps-library/src/utils/screen.test.ts Adds unit tests for centerAutoScalerVertically().
edge-apps/edge-apps-library/src/utils/calendar.ts Adds timezone-anchored getDateRangeForViewMode() shared calendar date-range helper.
edge-apps/edge-apps-library/src/utils/calendar.test.ts Adds unit tests for getDateRangeForViewMode().
edge-apps/edge-apps-library/src/types/index.ts Introduces CalendarViewMode and CALENDAR_VIEW_MODE types/constants.
edge-apps/edge-apps-library/src/components/register.ts Registers the new calendar view custom elements for global availability.
edge-apps/edge-apps-library/src/components/index.ts Exports the new calendar view components (and CalendarEvent type) from the components entrypoint.
edge-apps/edge-apps-library/src/components/vite-env.d.ts Adds *.css?inline typing for component CSS handling.
edge-apps/edge-apps-library/src/components/calendar-views/event-layout.ts Adds overlap clustering + column/span layout computation used by daily/weekly views.
edge-apps/edge-apps-library/src/components/calendar-views/calendar-view-utils.ts Adds shared DOM builders for gutters and event rendering (clipping/compact variants).
edge-apps/edge-apps-library/src/components/calendar-views/weekly-calendar-view/weekly-calendar-view-utils.ts Adds weekly windowing helpers (time slots, window filtering, style computation).
edge-apps/edge-apps-library/src/components/calendar-views/weekly-calendar-view/weekly-calendar-view-styles.ts Adds weekly view component CSS.
edge-apps/edge-apps-library/src/components/calendar-views/weekly-calendar-view/index.ts Exports weekly view component entrypoint.
edge-apps/edge-apps-library/src/components/calendar-views/daily-calendar-view/daily-calendar-view.ts Adds daily view custom element using shared layout/windowing and current-time indicator.
edge-apps/edge-apps-library/src/components/calendar-views/daily-calendar-view/daily-calendar-view-styles.ts Adds daily view component CSS.
edge-apps/edge-apps-library/src/components/calendar-views/daily-calendar-view/index.ts Exports daily view component entrypoint.
edge-apps/edge-apps-library/src/components/calendar-views/schedule-calendar-view/schedule-calendar-view.ts Adds schedule view custom element (“Today/Tomorrow” list) using shared formatting.
edge-apps/edge-apps-library/src/components/calendar-views/schedule-calendar-view/schedule-calendar-view-styles.ts Adds schedule view component CSS.
edge-apps/edge-apps-library/src/components/calendar-views/schedule-calendar-view/index.ts Exports schedule view component entrypoint.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

nicomiguelino and others added 2 commits March 19, 2026 15:12
- Set `default_value` to `''` instead of `primary` (a Google Calendar concept)
- Update help text to clarify that leaving blank uses the default calendar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… works

- Remove try/catch from `refreshToken` in both google-calendar and outlook-calendar
- Wrap only the initial startup call in try/catch to avoid crashing on first fetch
- Previously, swallowed errors caused initTokenRefreshLoop to never apply
  exponential backoff or stop after repeated failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nicomiguelino nicomiguelino changed the title feat(calendar): redesign app using plain HTML/CSS/TS feat(calendar): redesign calendar apps using plain HTML/CSS/TS + Web Components Mar 20, 2026
Copy link
Contributor

@rusko124 rusko124 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. Single-letter / abbreviated variable names

  • event-layout.ts: for (const e of cluster) — should be event or clusterEvent
  • tz parameter used throughout all view components — timezone would be more explicit
  • schedule-calendar-view.ts: (e) => { ... } in .filter() calls — should use event

2. Deep nesting / long functions

  • weekly-calendar-view.ts:_buildDayColumn() is ~60 lines mixing header construction, event filtering, layout lookup, and time indicator placement. Could extract _buildDayHeader() and _buildDayEventsArea().
  • calendar-view-utils.ts:buildEventElement() is ~50 lines of DOM manipulation with an if (isCompact) branch — candidate for two builder functions.
  • event-layout.ts:calculateClusterLayouts() does column assignment, column grouping, AND span calculation in one pass. Three distinct responsibilities in one function.

3. main.ts across all three apps is nearly identical.
The DOMContentLoaded handler in calendar/src/main.ts, google-calendar/src/main.ts, and outlook-calendar/src/main.ts share ~80% of the same code (scaler setup, theme, tick interval, element selection, active element logic). The only differences are token management and the fetch function. This could be a shared initCalendarApp() in the library with a fetchEvents callback parameter.

4. style.css is byte-for-byte identical across all three apps.
Should be a shared CSS file in edge-apps-library or imported from a common location.

5. getEventKey collision risk
event-layout.ts:181getEventKey uses startTime|endTime|title. Two events with the same title at the same time (e.g., recurring events across calendars) will collide and share a layout. Consider including a unique ID if available.

6. getWindowStartHour logic
weekly-calendar-view-utils.ts:16currentHour > 12 ? 13 : currentHour means at exactly noon, the window starts at hour 12 (12:00–00:00). At 13:00, it snaps to 13:00–01:00. There's a 1-hour window discontinuity. Is this intentional?

7. Schedule view hides ongoing events
schedule-calendar-view.ts_getVisibleEvents filters today's events with start.isAfter(nowInTz), meaning events that started before "now" are hidden. A 2-hour meeting that started 30 minutes ago disappears from the schedule view.

8. calendar/src/main.ts:8(screenly.settings.calendar_mode as string) || 'schedule' accesses settings directly, while google-calendar and outlook-calendar use getSettingWithDefault('calendar_mode', 'schedule'). Should be consistent.

9. calendar-view-utils.ts:52 — the magic number (45 / 60) * (100 / 12) for the compact event threshold deserves a named constant.

nicomiguelino and others added 8 commits March 24, 2026 11:02
…calendar views

- Replace `tz` parameter/local variable with `timezone` across all calendar-view files
- Replace single-letter `e` with `event` in filter callbacks in schedule-calendar-view

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Extract _buildDayHeader() and _buildDayEventsArea() from _buildDayColumn()
- Extract buildCompactEventItem() and buildFullEventItem() from buildEventElement()
- Split calculateClusterLayouts() into assignColumns(), groupByColumn(), and calculateColumnSpan()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ollisions

- Add optional `id` field to CalendarEvent interface
- Prefer `id` over the startTime|endTime|title composite key in getEventKey
- Pass uid from iCal, id from Google Calendar API, and id from Microsoft Graph API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add comment explaining the window locks to 13:00 past noon intentionally

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…alendar-view

- Extract TimeSlot, getWindowStartHour, generateTimeSlots, and filterEventsForWindow into calendar-window-utils.ts
- Update daily-calendar-view and weekly-calendar-view to import from the new shared module
- Remove cross-directory import from daily-calendar-view into weekly-calendar-view

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- fix filter to include events started before now but not yet ended
- rename nowInTz to now to avoid abbreviated variable name
- add ongoing event to screenshot fixtures in all three apps
- regenerate screenshots for calendar, google-calendar, outlook-calendar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- use getSettingWithDefault for calendar_mode in calendar/src/main.ts
- extract COMPACT_THRESHOLD_PCT constant with explanatory comment
- rename Pct suffix to Percent across all calendar-view files
- rename nowInTz to currentTime to avoid abbreviated variable names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- extract shared calendar-app.css to edge-apps-library/src/styles
- add initCalendarApp() to edge-apps-library for shared app bootstrap logic
- add getCalendarDateRange() to consolidate calendar_mode mapping and date range logic
- replace per-app boilerplate in main.ts with initCalendarApp()
- replace per-app style.css with @import from shared library

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nicomiguelino nicomiguelino requested a review from rusko124 March 25, 2026 00:24
Copy link
Collaborator

@salmanfarisvp salmanfarisvp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calender App Portaitd mode Is UI is not as expected, @nicomiguelino Please check that.

Image

- change orientation="landscape" to orientation="auto" in all three apps
- auto-scaler now detects viewport orientation and adapts accordingly
- portrait viewports no longer forced into landscape letterbox layout
- regenerate screenshots for all portrait resolutions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@salmanfarisvp salmanfarisvp self-requested a review March 25, 2026 14:24
Copy link
Collaborator

@salmanfarisvp salmanfarisvp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

- rename COMPACT_THRESHOLD_PCT to COMPACT_THRESHOLD_PERCENT
- rename tz parameter to timezone in getDateRangeForViewMode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@nicomiguelino nicomiguelino merged commit 2f60184 into master Mar 25, 2026
21 checks passed
@nicomiguelino nicomiguelino deleted the feat/overhaul-calendar-app branch March 25, 2026 15:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants